﻿using System;
using System.ComponentModel;

namespace Best.SmartCms.Utility
{
    /// <summary>
    /// 地图帮助类
    /// </summary>
    public class MapHelper
    {
        #region 地理位置距离计算


        private const double EarthRadius = 6371.393;

        private static double Rad(double d)
        {
            return d * Math.PI / 180.0;
        }

        /// <summary>
        /// 两个坐标点距离
        /// </summary>
        /// <param name="lng1">经度1</param>
        /// <param name="lat1">纬度1</param>
        /// <param name="lng2">经度2</param>
        /// <param name="lat2">纬度2</param>
        /// <returns>单位米 小数点后一位</returns>
        public static double GetPositionDistance(double lng1, double lat1, double lng2, double lat2)
        {
            double radLat1 = Rad(lat1);
            double radLat2 = Rad(lat2);
            double a = radLat1 - radLat2;
            double b = Rad(lng1) - Rad(lng2);
            double distance = 2 * Math.Asin(Math.Sqrt(Math.Pow(Math.Sin(a / 2), 2) +
                                                      Math.Cos(radLat1) * Math.Cos(radLat2) *
                                                      Math.Pow(Math.Sin(b / 2), 2)));
            distance = distance * EarthRadius;
            distance = Math.Round(distance * 10000) / 10000;
            return Math.Round(distance * 1000, 2);
        }

        /// <summary>
        /// 两个坐标点距离
        /// </summary>
        /// <param name="lng1">经度1</param>
        /// <param name="lat1">纬度1</param>
        /// <param name="lng2">经度2</param>
        /// <param name="lat2">纬度2</param>
        /// <returns>单位米 小数点后一位</returns>
        public static double GetPositionDistance(string lng1, string lat1, string lng2, string lat2)
        {
            return GetPositionDistance(String2Long(lng1, 0),
                String2Long(lat1, 0), String2Long(lng2, 0),
                String2Long(lat2, 0));
        }

        /// <summary>
        /// string型转换为float型
        /// </summary>
        /// <param name="strValue">要转换的字符串</param>
        /// <param name="defValue">缺省值，或者转换失败的值</param>
        /// <returns>转换后的数字类型结果</returns>
        public static double String2Long(string strValue, double defValue)
        {
            if (string.IsNullOrWhiteSpace(strValue))
            {
                return defValue;
            }

            double intValue;
            if (double.TryParse(strValue, out intValue))
            {
                return intValue;
            }

            return defValue;
        }

        #endregion

        /*
         WGS-84坐标系
            为一种国际标准的大地坐标线，是目前GPS全球卫星定位系统使用广泛的坐标系。
            国际地图提供商使用的坐标系，如open street map。
           天地图的在线地图是WGS-84坐标系，Google Earth采用的也是WGS-84坐标系。
        GCJ-02坐标系
            火星坐标系GCJ-02也叫国家测绘局坐标系，国家规定国内出版的各种地图系统（包括电子形式），必须至少采用GCJ-02对地理位置进行首次加密。
            国行移动设备中定位获取的坐标数据使用的是GCJ-02坐标系。
            超图云在线地图，谷歌国内在线地图，高德地图，腾讯地图采用的都是GCJ-02坐标系。
        BD-09坐标系
            百度地图标准的坐标系，其实是百度地图在火星坐标系上进行了二次加密。
            百度地图针对海外地区提供的WGS-84坐标系。
        GPS定位点坐标
            原生ios或android库获取的定位点坐标为WGS-84。
            百度移动sdk获取的定位点坐标可以是BD-09或GCJ-02，默认BD-09。
            高德移动sdk，腾讯移动sdk等获取的定位点坐标为GCJ-02。
         */

        public static double Pi = 3.1415926535897932384626;
        public static double A = 6378140.0; //1975年国际椭球体长半轴
        public static double Ee = 0.0033528131778969143;

        public static Gps Gps84ToBd09(double lon, double lat)
        {
            if (OutOfChina(lon, lat))
            {
                return null;
            }

            double dLat = TransformLat(lon - 105.0, lat - 35.0);
            double dLon = TransformLon(lon - 105.0, lat - 35.0);
            double radLat = lat / 180.0 * Math.PI;
            double magic = Math.Sin(radLat);
            magic = 1 - Ee * magic * magic;
            double sqrtMagic = Math.Sqrt(magic);
            dLat = (dLat * 180.0) / ((A * (1 - Ee)) / (magic * sqrtMagic) * Math.PI);
            dLon = (dLon * 180.0) / (A / sqrtMagic * Math.Cos(radLat) * Math.PI);
            double mgLon = lon + dLon;
            double mgLat = lat + dLat;
            double z = Math.Sqrt(mgLon * mgLon + mgLat * mgLat) + 0.00002 * Math.Sin(mgLat * Math.PI);
            double theta = Math.Atan2(mgLat, mgLon) + 0.000003 * Math.Cos(mgLon * Math.PI);
            double longitude = z * Math.Cos(theta) + 0.0065;
            double latitude = z * Math.Sin(theta) + 0.006;
            return new Gps(longitude, latitude);
        }

        public static Gps Gps84ToGcj02(double lon, double lat)
        {
            if (OutOfChina(lon, lat))
            {
                return null;
            }

            double dLat = TransformLat(lon - 105.0, lat - 35.0);
            double dLon = TransformLon(lon - 105.0, lat - 35.0);
            double radLat = lat / 180.0 * Pi;
            double magic = Math.Sin(radLat);
            magic = 1 - Ee * magic * magic;
            double sqrtMagic = Math.Sqrt(magic);
            dLat = (dLat * 180.0) / ((A * (1 - Ee)) / (magic * sqrtMagic) * Pi);
            dLon = (dLon * 180.0) / (A / sqrtMagic * Math.Cos(radLat) * Pi);
            double mgLat = lat + dLat;
            double mgLon = lon + dLon;
            return new Gps(mgLon, mgLat);
        }

        public static Gps Gcj02ToGps84(double lon, double lat)
        {
            Gps gps = Transform(lon, lat);
            double lontitude = lon * 2 - gps.Longitude;
            double latitude = lat * 2 - gps.Latitude;
            return new Gps(lontitude, latitude);
        }

        public static Gps Gcj02ToBd09(double ggLon, double ggLat)
        {
            double x = ggLon, y = ggLat;
            double z = Math.Sqrt(x * x + y * y) + 0.00002 * Math.Sin(y * Pi);
            double theta = Math.Atan2(y, x) + 0.000003 * Math.Cos(x * Pi);
            double bdLon = z * Math.Cos(theta) + 0.0065;
            double bdLat = z * Math.Sin(theta) + 0.006;
            return new Gps(bdLon, bdLat);
        }

        public static Gps Bd09ToGcj02(double bdLon, double bdLat)
        {
            double x = bdLon - 0.0065, y = bdLat - 0.006;
            double z = Math.Sqrt(x * x + y * y) - 0.00002 * Math.Sin(y * Pi);
            double theta = Math.Atan2(y, x) - 0.000003 * Math.Cos(x * Pi);
            double gg_lon = z * Math.Cos(theta);
            double gg_lat = z * Math.Sin(theta);
            return new Gps(gg_lon, gg_lat);
        }

        public static Gps Bd09ToGps84(double bdLon, double bdLat)
        {
            Gps gcj02 = Bd09ToGcj02(bdLon, bdLat);
            Gps map84 = Gcj02ToGps84(gcj02.Longitude, gcj02.Latitude);
            return map84;
        }

        public static bool OutOfChina(double lon, double lat)
        {
            if (lon < 72.004 || lon > 137.8347)
                return true;
            return lat < 0.8293 || lat > 55.8271;
        }

        private static Gps Transform(double lon, double lat)
        {
            if (OutOfChina(lon, lat))
            {
                return new Gps(lon, lat);
            }

            double dLat = TransformLat(lon - 105.0, lat - 35.0);
            double dLon = TransformLon(lon - 105.0, lat - 35.0);
            double radLat = lat / 180.0 * Pi;
            double magic = Math.Sin(radLat);
            magic = 1 - Ee * magic * magic;
            double sqrtMagic = Math.Sqrt(magic);
            dLat = (dLat * 180.0) / ((A * (1 - Ee)) / (magic * sqrtMagic) * Pi);
            dLon = (dLon * 180.0) / (A / sqrtMagic * Math.Cos(radLat) * Pi);
            double mgLat = lat + dLat;
            double mgLon = lon + dLon;
            return new Gps(mgLon, mgLat);
        }

        private static double TransformLat(double x, double y)
        {
            double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y
                         + 0.2 * Math.Sqrt(Math.Abs(x));
            ret += (20.0 * Math.Sin(6.0 * x * Pi) + 20.0 * Math.Sin(2.0 * x * Pi)) * 2.0 / 3.0;
            ret += (20.0 * Math.Sin(y * Pi) + 40.0 * Math.Sin(y / 3.0 * Pi)) * 2.0 / 3.0;
            ret += (160.0 * Math.Sin(y / 12.0 * Pi) + 320 * Math.Sin(y * Pi / 30.0)) * 2.0 / 3.0;
            return ret;
        }

        private static double TransformLon(double x, double y)
        {
            double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1
                         * Math.Sqrt(Math.Abs(x));
            ret += (20.0 * Math.Sin(6.0 * x * Pi) + 20.0 * Math.Sin(2.0 * x * Pi)) * 2.0 / 3.0;
            ret += (20.0 * Math.Sin(x * Pi) + 40.0 * Math.Sin(x / 3.0 * Pi)) * 2.0 / 3.0;
            ret += (150.0 * Math.Sin(x / 12.0 * Pi) + 300.0 * Math.Sin(x / 30.0
                                                                       * Pi)) * 2.0 / 3.0;
            return ret;
        }

        /// <summary>
        /// 坐标全部转换为腾讯
        /// </summary>
        /// <param name="lon"></param>
        /// <param name="lat"></param>
        /// <param name="maptype"></param>
        /// <returns></returns>
        public static Gps Trans2Tensen(double lon, double lat, MapType maptype)
        {
            Gps result = new Gps(lon, lat);
            if (maptype == MapType.Baidu)
            {
                var gps = Bd09ToGcj02(lon, lat);
                result.Latitude = gps.Latitude;
                result.Longitude = gps.Longitude;
            }

            return result;
        }
    }

    public class Gps
    {
        public double Longitude { get; set; }
        public double Latitude { get; set; }

        public Gps(double longitude, double latitude)
        {
            Longitude = longitude;
            Latitude = latitude;
        }
    }

    public enum MapType
    {
        /// <summary>
        /// 百度
        /// </summary>
        [Description("百度")]
        Baidu = 1,
        /// <summary>
        /// 谷歌
        /// </summary>
        [Description("谷歌")]
        Google = 2,
        /// <summary>
        /// 高德
        /// </summary>
        [Description("高德")]
        Gaode = 3,
        /// <summary>
        /// 腾讯
        /// </summary>
        [Description("腾讯")]
        Tencent = 4
    }
}